﻿using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Linq;
using TLD_NN;
using TLD_Vision;
using TLD_WFC;
using TLD_Vision.OCR;

namespace TLD_SOM_GUI
{
    public partial class SOMForm : Form
    {
        private const int SAMPLE_WIDTH = 20, SAMPLE_HEIGHT = 20;
        
        private SOM som;
        private double[][] trainingSet;
        private Boolean autoTrain = false;
        private MyPictureBox[] pictureBoxes;
        private Size inputSize;

        /// <summary>
        /// Initializes the UI and tries to open the trainingset.
        /// </summary>
        /// <param name="trainingSetLocation">Location to the folder containing the trainingset</param>
        public SOMForm(String trainingSetLocation)
        {
            InitializeComponent();
            pictureBoxes = Controls.OfType<MyPictureBox>().ToArray();
            inputSize = new Size(SAMPLE_WIDTH, SAMPLE_HEIGHT);

            OpenTrainingset(trainingSetLocation);
        }

        /// <summary>
        /// Tries to open the trainingset in the param. If this fails a folderbrowser dialog will be displayed.
        /// </summary>
        /// <param name="trainingSetLocation">Location to the folder containing the trainingset</param>
        private void OpenTrainingset(String trainingSetLocation)
        {
            Console.WriteLine("Loading trainingset");

            if (trainingSetLocation == null)
            {
                trainingSetLocation = OpenTrainingsetDialog();
                if (trainingSetLocation == null || !System.IO.Directory.Exists(trainingSetLocation))
                {
                    Console.WriteLine("Selected directory doesn't exist.");
                    return;
                }
            }

            string[] files = System.IO.Directory.GetFiles(trainingSetLocation);
            trainingSet = new double[files.Length][];

            for (int i = 0; i < files.Length; ++i)
            {
                String file = files[i];
                TLD_Vision.Image img = new TLD_Vision.Image(file);
                Rectangle rect = img.FindCharacterRectangle();
                TLD_Vision.Image ch = img.Crop(rect).Resize(SAMPLE_WIDTH, SAMPLE_HEIGHT);

                ImageData imgData = ch.GetImageData();

                //maps the pixels to 0.5 or -0.5, for efficiency and for binary input data
                //for more general purpose normalizing, use the Normalizer
                double[] bipolarData = new double[SAMPLE_HEIGHT * SAMPLE_WIDTH];
                for (int j = 0; j < imgData.Data.Length; ++j)
                {
                    bipolarData[j] = (imgData[j] & 0xFF) > 128 ? 0.5 : -0.5;
                }
                trainingSet[i] = bipolarData;
            }
            som = new SOM(trainingSet[0].Length, trainingSet.Length);

            som.InitializeNetwork();
        }

        /// <summary>
        /// Asks the user for the location of the trainingset.
        /// </summary>
        /// <returns>Location of the trainingset in a string or null if not succesfull.</returns>
        private String OpenTrainingsetDialog()
        {
            FolderBrowserDialog fbd = new FolderBrowserDialog();
            fbd.Description = "Choose a directory containing the trainingset for the neural network";
            if (fbd.ShowDialog() == DialogResult.OK)
            {
                return fbd.SelectedPath;
            }
            return null;
        }

        /// <summary>
        /// Runs the training iteration one time in background
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            if (trainingSet != null)
            {
                som.Train(trainingSet);
            }
        }

        /// <summary>
        /// Displays the weights of the last iteration on the screen.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show("Error: " + e.Error.Message);
            }
            else if (e.Cancelled)
            {
                MessageBox.Show("Algorithm canceled.");
            }
            else
            {
                if (trainingSet != null)
                {

                    // Bitmaps are fixed at 36.
                    TLD_Vision.Image[] bitmaps = new TLD_Vision.Image[36];

                    // Convert weights to bitmaps
                    for (int i = 0; i < trainingSet.Length && i < 36; i++)
                    {
                        TLD_Vision.Image image = MapUtility.SaveWeightMapImage(inputSize, som.outputNodes[i]);
                        bitmaps[i] = image;
                    }

                    UpdateBitmaps(bitmaps);
                    lLearningRate.Text = som.learningFactor.ToString();

                    if (autoTrain)
                    {
                        backgroundWorker.RunWorkerAsync();
                    }
                    else
                    {
                        btnStep.Enabled = true;
                    }
                }
                else
                {
                    OpenTrainingset(null);
                }
            }
        }

        /// <summary>
        /// Updates all 36 bitmaps on the screen.
        /// </summary>
        /// <param name="bitmaps">Array filled with bitmaps containing the weighted map. These are displayed on the GUI.</param>
        public void UpdateBitmaps(TLD_Vision.Image[] bitmaps)
        {
            for (int i = 0; i < bitmaps.Length && i < pictureBoxes.Length; ++i)
            {
                pictureBoxes[i].img = bitmaps[i];
            }
        }

        /// <summary>
        /// Starts the background worker to run one iteration or, if the trainingset is unset, asks the user for the set.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnStep_Click(object sender, EventArgs e) {
            if (trainingSet != null) {
                btnStep.Enabled = false;
                backgroundWorker.RunWorkerAsync();
            } else {
                OpenTrainingset(null);
            }
        }

        /// <summary>
        /// Toggles automatic training, if the trainingset is unset, asks the user for the set.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnAuto_Click(object sender, EventArgs e) {
            if (trainingSet != null) {
                autoTrain = !autoTrain;
                lAuto.Text = autoTrain ? "On" : "Off";
                btnStep.Enabled = false;
                if (autoTrain) {
                    backgroundWorker.RunWorkerAsync();
                }
            } else {
                OpenTrainingset(null);
            }
        }
    }
}
